package cn.com.zero.common.auth.security.filter;

import cn.com.zero.common.auth.security.token.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author xiongxj
 * @version 1.0.0
 * @Description 手机验证码登录的认证过滤器，参考 使用用户名密码登录的UsernamePasswordAuthenticationFilter 进行编写
 * @createTime 2023/3/21 11:38
 */
public class MobileCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    /**
     * 默认的手机号码参数名称
     */
    private static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";

    /**
     * 默认的手机验证码参数名称
     */
    private static final String SPRING_SECURITY_FORM_MOBILE_CODE_KEY = "mobileCode";

    /**
     * 默认的请求路径和请求方式
     */
    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login/mobileCode", "POST");

    /**
     * 是否只允许POST请求
     */
    private boolean postOnly = true;

    /**
     * 手机号码参数名称
     */
    private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;

    /**
     * 验证码参数名称
     */
    private String codeParameter = SPRING_SECURITY_FORM_MOBILE_CODE_KEY;

    /**
     * 构造函数
     */
    public MobileCodeAuthenticationFilter() {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (postOnly && !("POST").equals(request.getMethod())) {
            throw new AuthenticationServiceException("请求方式有误，不支持 " + request.getMethod() + " 请求");
        }

        // 获取手机号
        String mobile = getParameterValue(request, mobileParameter);
        mobile = (mobile != null) ? mobile.trim() : "";
        // 获取手机验证码
        String code = getParameterValue(request, codeParameter);
        code = (code != null) ? code : "";

        // AuthenticationProvider 里面有个supports方法,主要用来验证指定的token是否符合。
        // 可以通过指定不同类型的 token 来决定使用哪个 Provider.
        MobileCodeAuthenticationToken authRequest = new MobileCodeAuthenticationToken(mobile, code);
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    /**
     * 设置身份验证详情。
     * 此处使用protected，以便子类可自定义设置认证请求token对象
     *
     * @param request     请求对象
     * @param authRequest 认证请求token对象
     */
    protected <T extends AbstractAuthenticationToken> void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 请求参数值
     *
     * @param request   请求对象
     * @param parameter 请求参数名
     * @return 请求参数值
     */
    private String getParameterValue(HttpServletRequest request, String parameter) {
        return request.getParameter(parameter);
    }

    public void setMobileParameter(String mobileParameter) {
        Assert.hasText(mobileParameter, "Mobile parameter must not be empty or null");
        this.mobileParameter = mobileParameter;
    }

    public void setCodeParameter(String codeParameter) {
        Assert.hasText(codeParameter, "Mobile code parameter must not be empty or null");
        this.codeParameter = codeParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
}
